Preserve Workbench business rule payloads for clearer validation errors - #8048
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR preserves structured business-rule exception payloads through WorkBench uploads and resolves them into localized frontend messages. It adds payload sanitization, typed upload-result contracts, resolver registries, conflicting-record formatting, and backend/frontend coverage. ChangesBusiness Rule Payload Localization
Sequence Diagram(s)sequenceDiagram
participant WorkbenchUpload
participant BackendUploadHandler
participant to_failed_business_rule
participant resolveValidationMessage
participant resultMessageResolvers
WorkbenchUpload->>BackendUploadHandler: upload record
BackendUploadHandler->>to_failed_business_rule: exception and ReportInfo
to_failed_business_rule-->>WorkbenchUpload: FailedBusinessRule with payload
WorkbenchUpload->>resolveValidationMessage: validation key and payload
resolveValidationMessage->>resultMessageResolvers: resolve parsing or business-rule message
resultMessageResolvers-->>WorkbenchUpload: localized validation message
Suggested reviewers: 🚥 Pre-merge checks | ✅ 6✅ Passed checks (6 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
emenslin
left a comment
There was a problem hiding this comment.
- Confirm the message reads like a normal validation error, for example
Collectionobject must have unique catalognumber in collection. - Confirm the message includes conflicting record IDs when provided, for example
Conflicting record IDs: 3347460. - Confirm the tooltip no longer shows the raw Python tuple/dict payload.
Although this looks a lot better I do have one change I think is needed. I think that the error should show either the schema caption set by the user (e.g. CIDA Number instead of catalognumber) or the schema config field name (e.g. catalogNumber instead of catalognumber)
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
specifyweb/backend/workbench/upload/tests/test_upload_results_json.py (1)
41-70: ⚡ Quick winExtend this test to cover JSON round-trip of preserved payload.
This asserts conversion correctness, but not serialization/schema compatibility for the preserved payload. Add a small
UploadResult(...).to_json() -> json.dumps/json.loads -> from_jsonassertion in the same test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@specifyweb/backend/workbench/upload/tests/test_upload_results_json.py` around lines 41 - 70, Extend the testBusinessRuleExceptionPayload to also verify JSON round-trip: create the FailedBusinessRule via to_failed_business_rule(BusinessRuleException(...), info), wrap it in an UploadResult (use the same FailedBusinessRule instance), call UploadResult.to_json() then json.loads and pass that into UploadResult.from_json() (or the project equivalent) and assert the deserialized UploadResult still contains the original FailedBusinessRule payload (compare the preserved payload dict and message). Ensure you use the existing symbols: BusinessRuleException, ReportInfo, to_failed_business_rule, FailedBusinessRule, UploadResult.to_json and UploadResult.from_json for locating the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@specifyweb/backend/workbench/upload/upload_result.py`:
- Around line 253-261: The helper is_business_rule_exception_with_payload
currently accepts any dict as the payload (exception.args[1]) which can contain
non-serializable or invalid values; update the logic in
is_business_rule_exception_with_payload (and the similar check around lines
264-268) to validate and/or sanitize the payload shape before treating it as a
business-rule payload: ensure exception.args[1] is a dict whose keys are strings
and whose values are only JSON-serializable scalar types (str, int, float, bool,
None) or plain lists/dicts that recursively satisfy the same constraint, or else
reject it (or replace with a safe fallback like an empty dict or
{"unserializable": true}) so that constructing/encoding FailedBusinessRule will
not fail at runtime. Include references to
is_business_rule_exception_with_payload and the code path that constructs
FailedBusinessRule when applying this validation.
In `@specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts`:
- Around line 305-336: In resolveBackendBusinessRuleMessage, guard against
missing/empty table by checking the result of getStringPayload(payload, 'table')
(assigned to tableName) and return undefined immediately if it's falsy; this
prevents calling getSchemaTableLabel('') and producing empty localized table
labels. Keep the existing branches that use tableName (fieldNotUnique,
childFieldNotUnique) but only execute them when tableName is non-empty so
callers can fall back to the default message.
- Around line 259-269: The message suffix is hardcoded in English inside
withConflictingRecordIds, causing mixed locales; update withConflictingRecordIds
to fetch a localized prefix from backEndText (e.g., add a conflictingRecordIds
key to the backEndText dictionary) and use that localized string (via
localized/backEndText lookup) instead of the hardcoded "Conflicting record IDs:"
before joining payload.conflicting; ensure IR payload handling and existing
localized(message) wrapping remain unchanged so the full tooltip is entirely
localized.
---
Nitpick comments:
In `@specifyweb/backend/workbench/upload/tests/test_upload_results_json.py`:
- Around line 41-70: Extend the testBusinessRuleExceptionPayload to also verify
JSON round-trip: create the FailedBusinessRule via
to_failed_business_rule(BusinessRuleException(...), info), wrap it in an
UploadResult (use the same FailedBusinessRule instance), call
UploadResult.to_json() then json.loads and pass that into
UploadResult.from_json() (or the project equivalent) and assert the deserialized
UploadResult still contains the original FailedBusinessRule payload (compare the
preserved payload dict and message). Ensure you use the existing symbols:
BusinessRuleException, ReportInfo, to_failed_business_rule, FailedBusinessRule,
UploadResult.to_json and UploadResult.from_json for locating the code.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 5f43e892-a787-4b63-a34a-af5fa0eda045
📒 Files selected for processing (5)
specifyweb/backend/workbench/upload/tests/test_upload_results_json.pyspecifyweb/backend/workbench/upload/treerecord.pyspecifyweb/backend/workbench/upload/upload_result.pyspecifyweb/backend/workbench/upload/upload_table.pyspecifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts
emenslin
left a comment
There was a problem hiding this comment.
- Confirm the message reads like a normal validation error, for example
Collectionobject must have unique catalognumber in collection. - Confirm the message includes conflicting record IDs when provided, for example
Conflicting record IDs: 3347460. - Confirm the tooltip no longer shows the raw Python tuple/dict payload.
Looks good, I confirmed with CO and a few other tables and all errors seemed to be formatted correctly.
bhumikaguptaa
left a comment
There was a problem hiding this comment.
- Confirm the message reads like a normal validation error, for example
Collectionobject must have unique catalognumber in collection. - Confirm the message includes conflicting record IDs when provided, for example
Conflicting record IDs: 3347460. - Confirm the tooltip no longer shows the raw Python tuple/dict payload.
Works as expected, ran into no errors.
3a837da to
3de6ff5
Compare
|
@CodeRabbit full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@specifyweb/frontend/js_src/lib/components/WorkBench/resultMessageResolvers.ts`:
- Around line 146-177: Update fieldNotUnique and childFieldNotUnique to validate
the getStringPayload result for fieldName before resolving localized field
labels, falling back to the safe raw key when it is empty or missing. Apply the
same guard to parentField in childFieldNotUnique, while preserving the existing
table guard and normal localized-label behavior for non-empty values.
In `@specifyweb/frontend/js_src/lib/components/WorkBench/uploadResultTypes.ts`:
- Around line 169-189: Update the WbRecordResult type so it becomes a union of
single-variant objects: wrap each mapped recordResultType member in its
discriminator key, then index the mapped type by RecordResultTypes['type']. Keep
the existing Extract and Omit logic, ensuring record_result requires only the
one runtime variant rather than every possible discriminator key.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 94b978d5-866f-40a7-8838-b65088c496d8
📒 Files selected for processing (10)
specifyweb/backend/workbench/upload/tests/test_upload_results_json.pyspecifyweb/backend/workbench/upload/treerecord.pyspecifyweb/backend/workbench/upload/upload_result.pyspecifyweb/backend/workbench/upload/upload_table.pyspecifyweb/frontend/js_src/lib/components/LocalityUpdate/utils.tsspecifyweb/frontend/js_src/lib/components/WorkBench/__tests__/resultsParser.test.tsspecifyweb/frontend/js_src/lib/components/WorkBench/resultMessageResolvers.tsspecifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.tsspecifyweb/frontend/js_src/lib/components/WorkBench/uploadResultTypes.tsspecifyweb/frontend/js_src/lib/localization/backEnd.ts
g1rly-c0d3r
left a comment
There was a problem hiding this comment.
Testing instructions
- Confirm the message reads like a normal validation error.
- Confirm the message includes conflicting record IDs when provided.
- Confirm the tooltip no longer shows the raw Python tuple/dict payload.
- Proceed with same steps for other validation error types.
I tested with Catalog number, GUID, Accession number, and must-match agent first and last name and didn't find any errors.
rijulpoudel
left a comment
There was a problem hiding this comment.
- Confirm the message reads like a normal validation error.
- Confirm the message includes conflicting record IDs when provided.
- Confirm the tooltip no longer shows the raw Python tuple/dict payload.
- Proceed with same steps for other validation error types.
Got the expected message and it passes with other error types as well.


Fixes #8045
Improve Workbench handling of back-end business rule validation errors. The back-end now preserves structured
BusinessRuleExceptionpayloads inFailedBusinessRuleupload results instead of stringifying and discarding them. The Workbench front-end uses those payloads to show uniqueness-rule failures and appends the conflicting record IDs when available. This makes duplicate catalog number errors clearer and avoids exposing raw Python exception tuples in validation tooltips.Checklist
self-explanatory (or properly documented)
Testing instructions
Summary by CodeRabbit